package indi.fritz.bigmap.volme.indexing;

import java.io.IOException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;

import indi.fritz.bigmap.common.DBException;
import indi.fritz.bigmap.util.ByteUtil;

public class BTreeIndexEngine {
	static byte[] INDEXEX;
	static int SIZE = 1 + 8 + 8 + 8 + 256*8;
	
	static {
		byte[] bs = new byte[256];
		for (int i = 0; i < bs.length; i++) {
			bs[i] = (byte) (i-128);
		}
		BTreeIndexEngine.INDEXEX = bs;
	}
	
	byte key; //this key
	long parentOffset = -1;//parent offset
	long offset = -1;
	long value = -1; // this data
	long[] address = new long[256];// next key offset
	
	File file;
	RandomAccessFile reader;
	
	public BTreeIndexEngine(File file) throws IOException {
		this.file = file;
		if(!this.file.exists()) {
			throw new DBException.NotIndexFile();
		}
		reader = new RandomAccessFile(file, "rw");
	}
	
	private BTreeIndexEngine(RandomAccessFile reader, long offset, int length) throws IOException {
		this.reader = reader;
		reader.seek(offset);
		byte[] bs = new byte[length];
		reader.read(bs);
		this.key = bs[0];
		this.parentOffset = ByteUtil.byteToLong(ByteUtil.sub(bs, 1, 8));
		this.offset = ByteUtil.byteToLong(ByteUtil.sub(bs, 1 + 8, 8));
		this.value = ByteUtil.byteToLong(ByteUtil.sub(bs, 1 + 8 + 8, 8));
		if(length == SIZE) {
			int index = 1 + 8 + 8 + 8;
			for (int i = 0; i < address.length; i++) {
				address[i] = ByteUtil.byteToLong(ByteUtil.sub(bs, index, 8));
				index+= 8;
			}
		}
	}
	
	private BTreeIndexEngine(RandomAccessFile reader, byte key, long parentOffset, long offset) {
		this.reader = reader;
		this.key = key;
		this.parentOffset = parentOffset;
		this.offset = offset;
	}
	
	private BTreeIndexEngine writeNext(byte key,boolean hasChild) throws IOException {
		BTreeIndexEngine next = this.readNext(key, true);
		if(next == null) {//分裂下一个节点
			int index = (int) key + 128;
			synchronized (reader) {
				try(FileLock lock = this.reader.getChannel().lock()){
					this.address[index] = reader.length();
				}
			}
			next = new BTreeIndexEngine(reader, key, this.offset, this.address[index]);
			next.write(hasChild);
			long addressOffset = this.offset + 1 + 8 + 8 + 8 + (8 * index);
			synchronized (reader) {
				try(FileLock lock = this.reader.getChannel().lock(this.offset, SIZE, false)){
					reader.seek(addressOffset);
					reader.write(ByteUtil.longToByte(this.address[index]));
				}
			}
		}
		return next;
	}
	
	private void writeValue(long value) throws IOException {
		this.value = value;
		synchronized (reader) {
			try(FileLock lock = this.reader.getChannel().lock(this.offset, SIZE, false)){
				reader.seek(this.offset + 1 + 8 + 8);
				reader.write(ByteUtil.longToByte(this.value));
			}
		}
	}

	private BTreeIndexEngine write(boolean hasChild) throws IOException {
		byte[] data = new byte[] { key };
		data = ByteUtil.merge(data, ByteUtil.longToByte(parentOffset));
		data = ByteUtil.merge(data, ByteUtil.longToByte(offset));
		data = ByteUtil.merge(data, ByteUtil.longToByte(value));
		if(hasChild) {
			for (long l : address) {
				data = ByteUtil.merge(data, ByteUtil.longToByte(l));
			}
			if(data.length != SIZE) {
				throw new RuntimeException("length");
			}
		}else {
			if(data.length != (1 + 8 + 8 + 8)) {
				throw new RuntimeException("length");
			}
		}
		/**
		 * 禁止别的程序读取当前索引内容
		 */
		synchronized (reader) {
			try(FileLock lock = this.reader.getChannel().lock(this.offset, SIZE, false)){
				reader.seek(this.offset);
				reader.write(data);
			}
		}
		return this;
	}
	
	private BTreeIndexEngine readNext(byte b, boolean hasChild) throws IOException {
		int index = (int) b + 128;
		long offseet = address[index];
		if(offseet < 1) { return null; }
		return new BTreeIndexEngine(this.reader, offseet, hasChild ? SIZE : 1 + 8 + 8 + 8);
	}
	
	private BTreeIndexEngine getRoot() throws IOException {
		synchronized (reader) {
			try(FileLock lock = this.reader.getChannel().lock(0, SIZE, true)){
				if(reader.length() > 0) {
					return new BTreeIndexEngine(this.reader, 0, SIZE);
				}
			}
		}
		return new BTreeIndexEngine(reader, (byte)0x00, 0, 0).write(true);
	}
	
	public void write(String key, long index) throws IOException {
		byte[] hashKey = ByteUtil.intToByteArray(key.hashCode());
		BTreeIndexEngine root = getRoot();
		for (int i = 0; i < 4; i++) {
			byte b = hashKey[i];
			root = root.writeNext(b, i<3);
		}
		root.writeValue(index);
	}
	
	public Long read(String key) throws IOException {
		byte[] hashKey = ByteUtil.intToByteArray(key.hashCode());
		BTreeIndexEngine root = getRoot();
		for (int i = 0; i < 4; i++) {
			byte b = hashKey[i];
			root = root.readNext(b, i < 3);
			if(root == null) { return null; }
		}
		return root.value;
	}
	
	public static void main(String[] args) throws FileNotFoundException, IOException {
		File file = new File("/home/yanfan/temp/tree", "index");
		if(!file.exists()) { file.createNewFile(); }
		BTreeIndexEngine index = new BTreeIndexEngine(file);
		int i = 1000000;
		while(i-->0){
			index.write("key" + i, i);
		}
		i = 1000000;
		while(i-->0){
			System.out.println(index.read("key" + i));
		}
	}

	public RandomAccessFile getWR() {
		return this.reader;
	}
}